/* jtk-cocoa.m */

#import <Cocoa/Cocoa.h>
#include <jtk.h>
#include <cairo-quartz.h>

#define jtk_point_from_ns_point(point) ((jtk_point) { (double) point.x, (double) point.y })

enum jtk_event_type jtk_event_type_from_cocoa(NSEventType type)
{
    switch (type)
    {
    case NSLeftMouseDown:
    case NSRightMouseDown:
    case NSOtherMouseDown:
        return JTK_EVENT_MOUSE_DOWN;

    case NSLeftMouseUp:
    case NSRightMouseUp:
    case NSOtherMouseUp:
        return JTK_EVENT_MOUSE_UP;

    case NSLeftMouseDragged:
    case NSRightMouseDragged:
    case NSOtherMouseDragged:
        return JTK_EVENT_MOUSE_DRAG;

    case NSKeyDown:
        return JTK_EVENT_KEY_DOWN;

    default:
        return 0;
    }
}

@interface NSApplication (JTKAdditions)
- (void) setAppleMenu:(NSMenu *)menu;
@end

@interface JTKView : NSView
@end

static JTKView *jtk_main_view;

@implementation JTKView

- (void) drawRect:(NSRect)rect
{
    if (jtk_callback_list[JTK_DRAW] == JTK_NULL_CALLBACK)
        return;

    /* Here we totally ignored the rect argument of this method 
     * and always use the complete frame of this view to create
     * such a rendering context. This will ensure we can draw to
     * every place we want in users' drawing callback. */
    NSSize size = [self bounds].size;
    CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
    CGContextSaveGState(context);

    cairo_surface_t *surface = cairo_quartz_surface_create_for_cg_context(context, 
                                                                          (int) size.width, 
                                                                          (int) size.height);
    cairo_t *cr = cairo_create(surface);

    jtk_callback_list[JTK_DRAW](cr);

    cairo_destroy(cr);
    cairo_surface_destroy(surface);

	CGContextRestoreGState(context);
}

- (BOOL) acceptsFirstResponder
{
    return YES;
}

- (jtk_event) mouseEventFrom: (NSEvent *)event
{
    NSPoint point = [self convertPoint: [event locationInWindow] 
                              fromView: nil];

    jtk_event evt;

    evt.type = jtk_event_type_from_cocoa([event type]);
    evt.u.point = jtk_point_from_ns_point(point);
    evt.timestamp = [event timestamp];

    return evt;
}

- (jtk_event) keyEventFrom: (NSEvent *)event
{
    unichar ch = [[event characters] characterAtIndex: 0];
    jtk_event evt;

    evt.type = jtk_event_type_from_cocoa([event type]);
    evt.u.key = ch;
    evt.timestamp = [event timestamp];

    return evt;
}

- (void) mouseDown:(NSEvent *)event
{
    if (jtk_callback_list[JTK_MOUSE_DOWN] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = [self mouseEventFrom: event];
    jtk_callback_list[JTK_MOUSE_DOWN](&evt);
}

- (void) mouseDragged:(NSEvent *)event
{
    if (jtk_callback_list[JTK_MOUSE_DRAG] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = [self mouseEventFrom: event];
    jtk_callback_list[JTK_MOUSE_DRAG](&evt);
}

- (void) mouseUp:(NSEvent *)event
{
    if (jtk_callback_list[JTK_MOUSE_UP] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = [self mouseEventFrom: event];
    jtk_callback_list[JTK_MOUSE_UP](&evt);
}

- (void) keyDown:(NSEvent *)event
{
    if (jtk_callback_list[JTK_KEY_DOWN] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = [self keyEventFrom: event];
    jtk_callback_list[JTK_KEY_DOWN](&evt);
}

/* cairo quartz surface expects a flipped quartz context, and since
 * we also need flipped coordinates for mouse events, so we should
 * make the whole view flipped instead of do tranformation after we
 * retrieved such a Quartz context in -drawRect:. */
- (BOOL) isFlipped
{
    return YES;
}

@end

static void jtk_create_window(const char *title, int width, int height)
{
    NSWindow *window;
    JTKView *view;

    NSRect contentRect;
    unsigned int style;

    contentRect = NSMakeRect(0, 0, width, height);
    style = NSTitledWindowMask | NSMiniaturizableWindowMask | 
            NSClosableWindowMask | NSResizableWindowMask;

    window = [[NSWindow alloc] initWithContentRect: contentRect
                                         styleMask: style
                                           backing: NSBackingStoreBuffered
                                             defer: NO];
    [window setOpaque: YES];
    [window center];
    [window setViewsNeedDisplay: NO];
    [window setTitle: [NSString stringWithUTF8String: title]];
    [window makeKeyAndOrderFront: nil];

    NSRect viewRect = NSMakeRect(0, 0, width, height);
    view = [[JTKView alloc] initWithFrame: viewRect];
    [[window contentView] addSubview: view];
    [window makeFirstResponder: view];

    jtk_main_view = view;
}

static void jtk_create_menus(const char *app_title)
{
    [NSApp setMainMenu: [[[NSMenu alloc] init] autorelease]];

    NSMenu *appleMenu;
    NSMenuItem *menuItem;
    NSString *title;
    NSString *appName;

    appName = [NSString stringWithUTF8String: app_title];
    appleMenu = [[NSMenu alloc] initWithTitle: @""];

    /* Add menu items */
    title = [@"About " stringByAppendingString: appName];
    [appleMenu addItemWithTitle: title 
                         action: @selector(orderFrontStandardAboutPanel:) 
                  keyEquivalent: @""];

    [appleMenu addItem:[NSMenuItem separatorItem]];

    title = [@"Hide " stringByAppendingString: appName];
    [appleMenu addItemWithTitle:title action: @selector(hide:) keyEquivalent: @"h"];

    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle: @"Hide Others" 
                                                  action: @selector(hideOtherApplications:) 
                                           keyEquivalent: @"h"];
    [menuItem setKeyEquivalentModifierMask: (NSAlternateKeyMask | NSCommandKeyMask)];

    [appleMenu addItemWithTitle: @"Show All" 
                         action: @selector(unhideAllApplications:) 
                  keyEquivalent: @""];

    [appleMenu addItem:[NSMenuItem separatorItem]];

    title = [@"Quit " stringByAppendingString: appName];
    [appleMenu addItemWithTitle: title 
                         action: @selector(terminate:) 
                  keyEquivalent: @"q"];

    /* Put menu into the menubar */
    menuItem = [[NSMenuItem alloc] initWithTitle: @"" 
                                          action: nil 
                                   keyEquivalent: @""];
    [menuItem setSubmenu: appleMenu];
    [[NSApp mainMenu] addItem: menuItem];

    /* Tell the application object that this is now the application menu */
    [NSApp setAppleMenu: appleMenu];

    /* Finally give up our references to the objects */
    [appleMenu release];
    [menuItem release];
}

void jtk_init(const char *title, int width, int height)
{
    int i;

    for (i = 0; i < JTK_TOTAL_CALLBACKS; i++)
        jtk_callback_list[i] = JTK_NULL_CALLBACK;

    /* All AppKit operations are heavily relied on NSAutoreleasePool to 
     * run. So we'd better create one now. */
    NSAutoreleasePool *pool;
    pool = [NSAutoreleasePool new];

    /* This will ensure NSApp instance is initialized. */
    [NSApplication sharedApplication];

    /* We don't use Interface Builder to build up the interface, so menus
     * won't be created automatically, we have to do it by ourselves. */
    jtk_create_menus(title);
    jtk_create_window(title, width, height);

    /* Bring our app to foreground, background apps don't appear in dock or
     * accept keyboard focus. */
    const ProcessSerialNumber psn = { 0, kCurrentProcess };
    TransformProcessType(&psn, kProcessTransformToForegroundApplication);

    /* Also raise our app to front, otherwise our window will remain under 
     * the terminal. */
    SetFrontProcess(&psn);
}

void jtk_main_loop()
{
    [NSApp run];
}

void jtk_redraw()
{
    [jtk_main_view setNeedsDisplay: YES];
}

void jtk_quit()
{
    [NSApp stop: nil];
}

/* vim: set foldmethod=syntax: */

